#    Tichy
#
#    copyright 2008 Guillaume Chereau (charlie137@gmail.com)
#
#    This file is part of Tichy.
#
#    Tichy is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Tichy is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Tichy.  If not, see <http://www.gnu.org/licenses/>.

import logging
logger = logging.getLogger('xwindow')

from widget import Widget
try:
    import Xlib
    import Xlib.display
    import Xlib.protocol.event
    import Xlib.ext.xtest
    import Xlib.XK

    # TODO: the problem with the special X keycode is that pygame doesn't
    #       have all the special characters, so we should allow a unicode
    #       to be used as the key for the map, and check for it using
    #       Xlib.XK.string_to_keysym.  See
    #       http://www.shallowsky.com/software/crikey/pykey-0.1 for
    #       example.
    import tichy.key as _keys

    XKEYCODES = {_keys.K_LEFT: Xlib.XK.XK_Left,
                 _keys.K_RIGHT: Xlib.XK.XK_Right,
                 _keys.K_UP: Xlib.XK.XK_Up,
                 _keys.K_DOWN: Xlib.XK.XK_Down,
                 _keys.K_TAB: Xlib.XK.XK_Tab,
                 _keys.K_RETURN: Xlib.XK.XK_Return,
                 _keys.K_ESCAPE: Xlib.XK.XK_Escape,
                 _keys.K_EXCLAIM: Xlib.XK.XK_exclam,
                 _keys.K_HASH: Xlib.XK.XK_numbersign,
                 # _keys.K_ : Xlib.XK.XK_Percent,
                 _keys.K_DOLLAR: Xlib.XK.XK_dollar,
                 _keys.K_AMPERSAND: Xlib.XK.XK_ampersand,
                 _keys.K_QUOTEDBL: Xlib.XK.XK_quotedbl,
                 _keys.K_QUOTE: Xlib.XK.XK_apostrophe,
                 _keys.K_LEFTPAREN: Xlib.XK.XK_parenleft,
                 _keys.K_RIGHTPAREN: Xlib.XK.XK_parenright,
                 _keys.K_ASTERISK: Xlib.XK.XK_asterisk,
                 _keys.K_EQUALS: Xlib.XK.XK_equal,
                 _keys.K_PLUS: Xlib.XK.XK_plus,
                 _keys.K_COMMA: Xlib.XK.XK_comma,
                 _keys.K_MINUS: Xlib.XK.XK_minus,
                 _keys.K_PERIOD: Xlib.XK.XK_period,
                 _keys.K_KP_DIVIDE: Xlib.XK.XK_slash,
                 _keys.K_COLON: Xlib.XK.XK_colon,
                 _keys.K_SEMICOLON: Xlib.XK.XK_semicolon,
                 _keys.K_LESS: Xlib.XK.XK_less,
                 _keys.K_GREATER: Xlib.XK.XK_greater,
                 _keys.K_QUESTION: Xlib.XK.XK_question,
                 _keys.K_AT: Xlib.XK.XK_at,
                 _keys.K_LEFTBRACKET: Xlib.XK.XK_bracketleft,
                 _keys.K_RIGHTBRACKET: Xlib.XK.XK_bracketright,
                 _keys.K_BACKSLASH: Xlib.XK.XK_backslash,
                 _keys.K_CARET: Xlib.XK.XK_asciicircum,
                 _keys.K_UNDERSCORE: Xlib.XK.XK_underscore,
                 _keys.K_BACKQUOTE: Xlib.XK.XK_grave,
                 _keys.K_BACKSPACE: Xlib.XK.XK_BackSpace}
                 # _keys.K_: Xlib.XK.XK_Braceleft,
                 # _keys.K_ : Xlib.XK.XK_Bar,
                 # '}' : Xlib.XK.XK_Braceright,
                 # '~' : Xlib.XK.XK_Asciitilde}

except ImportError, e:
    logger.error("can't import Xlib %s", e)
    Xlib = None

import pygame
import time


# XXX: we have a few busy wait in this class


class XWindow(Widget):
    """Experimental support for XWindow"""

    def __init__(self, parent, expand=True, **kargs):
        if not Xlib:
            raise Exception("No Xlib support")
        super(XWindow, self).__init__(parent, expand=expand, **kargs)
        self.x_window = None
        self.watch = None

    def __get_id(self):
        return self.x_window.id
    id = property(__get_id)

    def create_x_window(self):
        if self.x_window is not None:
            return
        if not Xlib:
            self.x_display = None
            return

        # First we get the xwindow id of the SDL window
        self.x_display = Xlib.display.Display()
        self.x_screen = self.x_display.screen()
        info = pygame.display.get_wm_info()
        x_window_id = info['window']
        x_window = self.x_display.create_resource_object('window',
                                                         x_window_id)
        pos = self.screen_pos()
        # Create a sub window
        self.x_window = x_window.create_window(
            pos.x, pos.y, self.size.x, self.size.y, 0,
            self.x_screen.root_depth,
            Xlib.X.CopyFromParent,
            Xlib.X.CopyFromParent,
            background_pixel=self.x_screen.white_pixel,
            colormap=Xlib.X.CopyFromParent,
            event_mask=Xlib.X.StructureNotifyMask)
        self.x_window.map()

        # Do a busy wait for X reply
        while True:
            event = self.x_display.next_event()
            if event.type == Xlib.X.MapNotify and \
                    event.window == self.x_window:
                break
        self.x_display.sync()

        # We get the Notify Events
        self.emit('exposed')

    def hide(self):
        if self.x_window:
            self.x_window.destroy()
            # Do a busy wait for X reply
            while 1:
                event = self.x_display.next_event()
                if event.type == Xlib.X.DestroyNotify and \
                        event.window == self.x_window:
                    break
        self.x_window = None

    def show(self):
        if not self.x_window:
            self.create_x_window()

    def organize(self):
        self.show()

    def destroy(self):
        self.hide()
        super(XWindow, self).destroy()

    def key_to_keycode(self, key):
        keysym = key.key

        if keysym in XKEYCODES:
            return self.x_display.keysym_to_keycode(XKEYCODES[keysym])

        ret = self.x_display.keysym_to_keycode(keysym)
        if ret:
            return ret
        logger.error("Can't find keycode for key %s",
                     pygame.key.name(key.key))
        return 0

    def key_down(self, key):
        # If a key down event arrives, we send it to the x_window
        if not key.key:
            return

        # XXX: This is a hack, should work only with the terminal application
        children = self.x_window.query_tree().children
        if children:
            children[-1].set_input_focus(Xlib.X.RevertToParent,
                                         Xlib.X.CurrentTime)

        if key.mod == 1:
            modifier = self.x_display.keysym_to_keycode(Xlib.XK.XK_Shift_L)
        else:
            modifier = None
        keycode = self.key_to_keycode(key)

        fake_input = Xlib.ext.xtest.fake_input

        if modifier:
            fake_input(self.x_display, Xlib.X.KeyPress, modifier)
            self.x_display.sync()
        fake_input(self.x_display, Xlib.X.KeyPress, keycode)
        self.x_display.sync()
        fake_input(self.x_display, Xlib.X.KeyRelease, keycode)
        self.x_display.sync()

        if modifier:
            self.x_display.sync()
            fake_input(self.x_display, Xlib.X.KeyRelease, modifier)
            self.x_display.sync()

        return True

    def start_app(self, *cmd):
        import subprocess

        self.enable_wm()
        process = subprocess.Popen(*cmd)
        return process

    def enable_wm(self):
        """Make the window automatically swallow every new created X window
        that have root as a parent

        This effectively turns the window into a windows manager.
        """
        # XXX: need to put `watch` method into tichy.mainloop
        import gobject

        logger.info("listening to display socket %s",
                    self.x_display.fileno())
        self.watch = gobject.io_add_watch(self.x_display.fileno(),
                                          gobject.IO_IN,
                                          self._on_x_display_socket_ready)
        self.x_screen.root.change_attributes(
            event_mask=Xlib.X.SubstructureNotifyMask)
        self.x_display.sync()

    def disable_wm(self):
        if self.watch:
            # XXX: need to put `watch` method into tichy.mainloop
            import gobject
            gobject.source_remove(self.watch)
            self.watch = None
            self.x_screen.root.change_attributes(event_mask=Xlib.X.NONE)

    def _on_x_display_socket_ready(self, source, cond):
        logger.debug("display socket ready")
        event = self.x_display.next_event()
        logger.debug("got event: %s", event)
        # We only reparent windows that are child of the root window
        # and that didn't set there overide attribute
        if event.type == Xlib.X.CreateNotify and \
                event.parent == self.x_screen.root and \
                not event.override:
            self._reparent(event.window, event.x, event.y)
        return True

    def _reparent(self, window, x, y):
        window.reparent(self.x_window, x, y)
        window.map()
        self.x_display.sync()
        # TODO: We shouldn't give the focus to this XWindow We
        #       should send the event in the key_down method
        self.x_display.set_input_focus(
            window, Xlib.X.RevertToParent, Xlib.X.CurrentTime)
